/**
  layer.c - Created by Timothy Morey on 6/11/2012
*/

#include <libxml/tree.h>
#include <limits.h>
#include <netcdf.h>
#include <string.h>

#ifdef __WINDOWS__
#include <Windows.h>
#include <GL/glew.h>
#endif

#include <GL/gl.h>
#include <GL/glu.h>

#include "data-source.h"
#include "err.h"
#include "layer.h"
#include "logger.h"
#include "variant.h"

#ifdef __WINDOWS__
#pragma warning(disable:4996)
#endif


/*****************************************************************************
 * Local definitions
 *****************************************************************************/

typedef struct
{
  LayerProperties Props;

  int* DataSource;
  FrameBuffer* SourceData;
  int NDataStreams;
  int PrimaryStream;
  int MaskStream;

  int CurrentFrame;
  Variant MinVal;
  Variant MaxVal;

  char* PixelBuffer;
  int PixelBufferLen;
} Layer;

#define MAX_LAYERS 32

static Layer* g_Layers[MAX_LAYERS];

int DataFrameReceived(int dsid, const char* frame, int len);
int ScanFrameMinMax(int layer, int frame, Variant* min, Variant* max);

int ValidLayer(int i) { return i >= 0 && i < MAX_LAYERS && g_Layers[i]; } 


/*****************************************************************************
 * Implementation of layer.h functions
 *****************************************************************************/

int CreateLayer(LayerProperties props, int* layer)
{
  int retval = NVN_NOERR;
  int tdimlen;
  Layer* lyr = 0;
  int pos = -1;
  int i = 0;

  if(! layer)
    retval = NVN_EINVARGS;

  if(NVN_NOERR == retval)
  {
    for(i = 0; i < MAX_LAYERS; i++)
    {
      if(0 == g_Layers[i])
      {
        g_Layers[i] = (Layer*)1;  // make sure nobody takes our spot
        pos = i;
        break;
      }
    }

    if(pos < 0)
      retval = NVN_EMAXOBJS;
  }

  if(NVN_NOERR == retval)
  {
    lyr = (Layer*)malloc(sizeof(Layer));
    memset(lyr, 0, sizeof(Layer));
    lyr->Props = props;
    lyr->MinVal = MaxVariant(props.DataSchema.DataType);
    lyr->MaxVal = MinVariant(props.DataSchema.DataType);
    lyr->PixelBuffer = 0;
    lyr->PixelBufferLen = 0;
    tdimlen = props.DataSchema.Dims[props.TDimension].Length;

    g_Layers[pos] = lyr;

    if(props.UseMask)
    {
      lyr->DataSource = (int*)malloc(2 * sizeof(int));
      lyr->SourceData = (FrameBuffer*)malloc(2 * sizeof(FrameBuffer));
      lyr->NDataStreams = 2;
      lyr->PrimaryStream = 0;
			lyr->MaskStream = 1;

      InitFrameBuffer(&lyr->SourceData[lyr->PrimaryStream], tdimlen);
      InitFrameBuffer(&lyr->SourceData[lyr->MaskStream], tdimlen);
    }
    else
    {
      lyr->DataSource = (int*)malloc(sizeof(int));
      lyr->SourceData = (FrameBuffer*)malloc(sizeof(FrameBuffer));
      lyr->NDataStreams = 1;
      lyr->PrimaryStream = 0;
      lyr->MaskStream = -1;

      InitFrameBuffer(&lyr->SourceData[lyr->PrimaryStream], tdimlen);
    }
  }

  if(NVN_NOERR == retval)
  {
    g_Layers[pos] = lyr;
    *layer = pos;
  }
  else
  {
    if(lyr)
      free(lyr);

    if(pos > -1)
      g_Layers[pos] = 0;
  }

  if(NVN_NOERR == retval)
  {
    if(props.UseMask)
    {
      DataSourceStartStream(props.DataSource, 
                            DataFrameReceived, 
                            &lyr->DataSource[lyr->PrimaryStream]);
      DataSourceStartStream(props.MaskSource, 
                            DataFrameReceived, 
                            &lyr->DataSource[lyr->MaskStream]);
    }
    else
    {
      DataSourceStartStream(props.DataSource, 
                            DataFrameReceived, 
                            &lyr->DataSource[lyr->PrimaryStream]);
    }
  }

  return retval;
}

int DestroyLayer(int layer)
{
  int retval = NVN_NOERR;
  int i = 0;

  if(ValidLayer(layer))
  {
    if(g_Layers[layer]->SourceData)
    {
      for(i = 0; i < g_Layers[layer]->NDataStreams; i++)
        DestroyFrameBuffer(&g_Layers[layer]->SourceData[i]);

      free(g_Layers[layer]->SourceData);
    }

    free(g_Layers[layer]);
    g_Layers[layer] = 0;
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int DrawLayer(int layer, int frame)
{
  int retval = NVN_NOERR;
  int count = 0;

  GetLayerFrameCount(layer, &count);
  if(ValidLayer(layer) && frame >= 0 && frame < count)
  {
    if(RendererTypePixelMap == g_Layers[layer]->Props.RendererType)
    {
      Frame f = g_Layers[layer]->SourceData[g_Layers[layer]->PrimaryStream].Frames[frame];
      DrawPixmapFrame(f.Buffer, 160, 100, GL_RGB, GL_UNSIGNED_BYTE_3_3_2);
    }
    else if(RendererTypeShadedGrid == g_Layers[layer]->Props.RendererType)
    {
      Frame valframe = g_Layers[layer]->SourceData[g_Layers[layer]->PrimaryStream].Frames[frame];

      if(g_Layers[layer]->PixelBuffer == 0 ||
         (size_t)g_Layers[layer]->PixelBufferLen < GetSliceLength(g_Layers[layer]->Props.DataSchema) * sizeof(int))
      {
        if(g_Layers[layer]->PixelBuffer)
          free(g_Layers[layer]->PixelBuffer);

        g_Layers[layer]->PixelBufferLen = GetSliceLength(g_Layers[layer]->Props.DataSchema) * sizeof(int);
        g_Layers[layer]->PixelBuffer = (char*)malloc(g_Layers[layer]->PixelBufferLen);
      }

      if(g_Layers[layer]->Props.UseMask)
      {
        Frame maskframe = g_Layers[layer]->SourceData[g_Layers[layer]->MaskStream].Frames[frame];
        DrawMaskedShadedGridFrame(g_Layers[layer]->Props.MaskDef, 
                                  g_Layers[layer]->Props.ColorRamp,
                                  g_Layers[layer]->Props.DataSchema, 
                                  g_Layers[layer]->Props.XDimension, 
                                  g_Layers[layer]->Props.YDimension, 
                                  g_Layers[layer]->MinVal, 
                                  g_Layers[layer]->MaxVal, 
                                  valframe.Buffer, 
                                  valframe.Length,
                                  g_Layers[layer]->Props.MaskSchema, 
                                  maskframe.Buffer, 
                                  maskframe.Length,
                                  g_Layers[layer]->PixelBuffer, 
                                  g_Layers[layer]->PixelBufferLen);
      }
      else
      {
        DrawShadedGridFrame(g_Layers[layer]->Props.ColorRamp, g_Layers[layer]->Props.DataSchema, 
                            g_Layers[layer]->Props.XDimension, g_Layers[layer]->Props.YDimension, 
                            g_Layers[layer]->MinVal, g_Layers[layer]->MaxVal, 
                            valframe.Buffer, valframe.Length,
                            g_Layers[layer]->PixelBuffer, g_Layers[layer]->PixelBufferLen);
      }
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int GetDataHeight(int layer, int* height)
{
  int retval = NVN_NOERR;

  if(ValidLayer(layer) && height)
  {
    *height = g_Layers[layer]->Props.DataSchema.Dims[g_Layers[layer]->Props.YDimension].Length;
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int GetDataWidth(int layer, int* width)
{
  int retval = NVN_NOERR;

  if(ValidLayer(layer) && width)
  {
    *width = g_Layers[layer]->Props.DataSchema.Dims[g_Layers[layer]->Props.XDimension].Length;
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int GetLayerCurrentFrame(int layer, int* frame)
{
  int retval = NVN_NOERR;
  int count;

  if(ValidLayer(layer) && g_Layers[layer]->SourceData && frame)
  {
    retval = GetLayerFrameCount(layer, &count);

    if(count > 0 && count > g_Layers[layer]->CurrentFrame)
      *frame = g_Layers[layer]->CurrentFrame;
    else
      retval = NVN_EINVFRAME;
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int GetLayerFrameCount(int layer, int* count)
{
  int retval = NVN_NOERR;
  int i = 0;
  int f = 0;

  if(ValidLayer(layer) && g_Layers[layer]->SourceData && count)
  {
    *count = INT_MAX;
    for(i = 0; i < g_Layers[layer]->NDataStreams; i++)
    {
      GetFrameCount(&g_Layers[layer]->SourceData[i], &f);
      *count = f < *count ? f : *count;
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int GetNextFrameIndex(int layer, int frame, int* next)
{
  int retval = NVN_NOERR;
  
  if(ValidLayer(layer) && next)
  {
    int count = 0;
    
    GetLayerFrameCount(layer, &count);
    if(count > 0)
    {
      *next = frame + 1;

      if(*next >= count)
        *next = 0;

      if(*next < 0)
        *next = 0;
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int HasCompleteFrame(int layer, int frame, int* complete)
{
  int retval = NVN_NOERR;
  int count = 0;

  if(ValidLayer(layer) && complete)
  {
    GetLayerFrameCount(layer, &count);
    *complete = 0 <= frame && frame < count;
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int ParseLayerProperties(xmlNodePtr root, LayerProperties* props)
{
  int retval = NVN_NOERR;
  xmlChar* value = 0;
  xmlNodePtr child;
  xmlNodePtr grandchild;

  if(root && props)
  {
    memset(props, 0, sizeof(LayerProperties));

    if(xmlHasProp(root, BAD_CAST "RendererType"))
    {
      value = xmlGetProp(root, BAD_CAST "RendererType");
      props->RendererType = (RendererType)atoi((char*)value);
      xmlFree(value);
    }

    if(xmlHasProp(root, BAD_CAST "XDimension"))
    {
      value = xmlGetProp(root, BAD_CAST "XDimension");
      props->XDimension = atoi((char*)value);
      xmlFree(value);
    }

    if(xmlHasProp(root, BAD_CAST "YDimension"))
    {
      value = xmlGetProp(root, BAD_CAST "YDimension");
      props->YDimension = atoi((char*)value);
      xmlFree(value);
    }

    if(xmlHasProp(root, BAD_CAST "TDimension"))
    {
      value = xmlGetProp(root, BAD_CAST "TDimension");
      props->TDimension = atoi((char*)value);
      xmlFree(value);
    }

    if(xmlHasProp(root, BAD_CAST "MaintainAspectRatio"))
    {
      value = xmlGetProp(root, BAD_CAST "MaintainAspectRatio");
      props->MaintainAspectRatio = atoi((char*)value);
      xmlFree(value);
    }

    child = root->children;
    while(child)
    {
      if(0 == xmlStrcmp(BAD_CAST "ColorRamp", child->name))
      {
        ParseColorRamp(child, &props->ColorRamp);
      }
      else if(0 == xmlStrcmp(BAD_CAST "DataSourceDescriptor", child->name))
      {
        ParseDataSourceDescriptor(child, &props->DataSource);
      }
      else if(0 == xmlStrcmp(BAD_CAST "DataGridSchema", child->name))
      {
        ParseDataGridSchema(child, &props->DataSchema);
      }
      else if(0 == xmlStrcmp(BAD_CAST "Mask", child->name))
      {
        props->UseMask = 1;
        grandchild = child->children;
        while(grandchild)
        {

          if(0 == xmlStrcmp(BAD_CAST "GridMaskDef", grandchild->name))
          {
            ParseGridMaskDef(grandchild, &props->MaskDef);
          }
          else if(0 == xmlStrcmp(BAD_CAST "DataSourceDescriptor", grandchild->name))
          {
            ParseDataSourceDescriptor(grandchild, &props->MaskSource);
          }
          else if(0 == xmlStrcmp(BAD_CAST "DataGridSchema", grandchild->name))
          {
            ParseDataGridSchema(grandchild, &props->MaskSchema);
          }

          grandchild = grandchild->next;
        }
      }

      child = child->next;
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int SaveLayerProperties(LayerProperties props, xmlNodePtr* root)
{
  int retval = NVN_NOERR;
  char temp[32];
  xmlNodePtr child = 0;
  xmlNodePtr grandchild = 0;

  if(root)
  {
    if(! *root)
      *root = xmlNewNode(0, BAD_CAST "LayerProperties");

    sprintf(temp, "%d", props.RendererType);
    xmlNewProp(*root, BAD_CAST "RendererType", BAD_CAST temp);

    child = 0;
    SaveColorRamp(props.ColorRamp, &child);
    xmlAddChild(*root, child);

    child = 0;
    SaveDataSourceDescriptor(props.DataSource, &child);
    xmlAddChild(*root, child);

    child = 0;
    SaveDataGridSchema(props.DataSchema, &child);
    xmlAddChild(*root, child);
    
    sprintf(temp, "%d", props.XDimension);
    xmlNewProp(*root, BAD_CAST "XDimension", BAD_CAST temp);
    
    sprintf(temp, "%d", props.YDimension);
    xmlNewProp(*root, BAD_CAST "YDimension", BAD_CAST temp);
    
    sprintf(temp, "%d", props.TDimension);
    xmlNewProp(*root, BAD_CAST "TDimension", BAD_CAST temp);
    
    sprintf(temp, "%d", props.MaintainAspectRatio);
    xmlNewProp(*root, BAD_CAST "MaintainAspectRatio", BAD_CAST temp);

    if(props.UseMask)
    {
      child = xmlNewNode(0, BAD_CAST "Mask");
      xmlAddChild(*root, child);

      grandchild = 0;
      SaveGridMaskDef(props.MaskDef, &grandchild);
      xmlAddChild(child, grandchild);

      grandchild = 0;
      SaveDataSourceDescriptor(props.MaskSource, &grandchild);
      xmlAddChild(child, grandchild);

      grandchild = 0;
      SaveDataGridSchema(props.MaskSchema, &grandchild);
      xmlAddChild(child, grandchild);
    }
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int SetLayerCurrentFrame(int layer, int frame)
{
  int retval = NVN_NOERR;

  if(ValidLayer(layer))
  {
    g_Layers[layer]->CurrentFrame = frame;
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}

int UpdateLayerTransform(int layer, int width, int height)
{
  int retval = NVN_NOERR;

  if(ValidLayer(layer) && width > 0 && height > 0)
  {
    int datawidth;
    int dataheight;
    float xscale = 1.0;
    float yscale = 1.0;

    GetDataWidth(layer, &datawidth);
    GetDataHeight(layer, &dataheight);
    
    if(g_Layers[layer]->Props.MaintainAspectRatio)
    {
      if(((float)datawidth / (float)dataheight) > ((float)width / (float)height))
        xscale = yscale = (float)width / (float)datawidth;
      else
        xscale = yscale = (float)height / (float)dataheight;
    }
    else
    {
      xscale = (float)width / (float)datawidth;
      yscale = (float)height / (float)dataheight;
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glPixelZoom(xscale, yscale);
    glRasterPos2i(0, 0);

    DrawLayer(layer, g_Layers[layer]->CurrentFrame);
  }
  else
  {
    retval = NVN_EINVARGS;
  }

  return retval;
}


/*****************************************************************************
 * Implementation of local functions
 *****************************************************************************/

int DataFrameReceived(int dsid, const char* frame, int len)
{
  int retval = NVN_NOERR;
  int i = 0;
  int j = 0;
  int fi = -1;
  int complete = 0;

  printf("Receiving...\n");

  for(i = 0; i < MAX_LAYERS; i++)
  {
    for(j = 0; ValidLayer(i) && j < g_Layers[i]->NDataStreams; j++)
    {
      if(dsid == g_Layers[i]->DataSource[j])
      {
        retval = AddFrameToBuffer(&g_Layers[i]->SourceData[j], frame, len, &fi);
        if(NVN_NOERR != retval)
        {
          WriteErrorMessage(retval, "AddFrameToBuffer failed");
        }
        else if(NVN_NOERR == HasCompleteFrame(i, fi, &complete) && complete)
        {
          ScanFrameMinMax(i, fi, &g_Layers[i]->MinVal, &g_Layers[i]->MaxVal);
        }

        // break out of the loop:
        j = g_Layers[i]->NDataStreams;
        i = MAX_LAYERS;
      }
    }
  }

  return retval;
}

int ScanFrameMinMax(int layer, int frame, Variant* min, Variant* max)
{
  int retval = NVN_NOERR;
  int i = 0;
  int width = 0;
  int height = 0;
  Variant v, mv, mmin, mmax;
  int maskcell = 0;
  size_t typesize = 0;
  char* buf;
  char* maskbuf;
  int count;

  printf("Scanning...\n");
  GetLayerFrameCount(layer, &count);
  if(ValidLayer(layer) && min && max && frame >= 0 && frame < count)
  {
    v.Type = g_Layers[layer]->MinVal.Type;
    width = g_Layers[layer]->Props.DataSchema.Dims[g_Layers[layer]->Props.XDimension].Length;
    height = g_Layers[layer]->Props.DataSchema.Dims[g_Layers[layer]->Props.YDimension].Length;
    buf = g_Layers[layer]->SourceData[g_Layers[layer]->PrimaryStream].Frames[frame].Buffer;
    
    if(g_Layers[layer]->Props.UseMask)
    {
      mmin = g_Layers[layer]->Props.MaskDef.MaskMin;
      mmax = g_Layers[layer]->Props.MaskDef.MaskMax;
      mv.Type = mmin.Type;
      maskbuf = g_Layers[layer]->SourceData[g_Layers[layer]->MaskStream].Frames[frame].Buffer;
    }

    for(i = 0; i < width * height; i++)
    {
      maskcell = 0;

      if(g_Layers[layer]->Props.UseMask)
      {
        switch(mv.Type)
        {
          case NC_BYTE:
            mv.Value.ByteVal = ((unsigned char*)maskbuf)[i]; break;
          case NC_CHAR:
            mv.Value.CharVal = maskbuf[i]; break;
          case NC_INT:
            mv.Value.IntVal = ((int*)maskbuf)[i]; break;
          case NC_FLOAT:
            mv.Value.FloatVal = ((float*)maskbuf)[i]; break;
          case NC_DOUBLE:
            mv.Value.DoubleVal = ((double*)maskbuf)[i]; break;
        }

        if((g_Layers[layer]->Props.MaskDef.Negative && VariantInRange(mv, mmin, mmax)) ||
           ((!g_Layers[layer]->Props.MaskDef.Negative) && (!VariantInRange(mv, mmin, mmax))))
        {
          maskcell = 1;
        }
      }

      if(! maskcell)
      {
        switch(v.Type)
        {
          case NC_BYTE:
            v.Value.ByteVal = ((unsigned char*)buf)[i]; break;
          case NC_CHAR:
            v.Value.CharVal = buf[i]; break;
          case NC_INT:
            v.Value.IntVal = ((int*)buf)[i]; break;
          case NC_FLOAT:
            v.Value.FloatVal = ((float*)buf)[i]; break;
          case NC_DOUBLE:
            v.Value.DoubleVal = ((double*)buf)[i]; break;
        }

        if(g_Layers[layer]->Props.DataSchema.FillValue.Type != VariantTypeNull &&
            VariantIsNearlyEqual(v, g_Layers[layer]->Props.DataSchema.FillValue))
        {
          maskcell = 1;
        }
      }

      if(! maskcell)
      {
        if(VariantCompare(v, *min) < 0)
          *min = v;

        if(VariantCompare(v, *max) > 0)
          *max = v;
      }
    }
  }

  return retval;
}
